package com.hw.backapi.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.amazonaws.services.s3.model.S3Object;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hw.backapi.context.AwsOssContext;
import com.hw.backapi.service.IDiagTaskService;
import com.hw.backapi.service.IOssService;
import com.hw.common.contants.PageInfoContants;
import com.hw.common.enums.TimePartitionEnum;
import com.hw.common.exception.BaseException;
import com.hw.common.utils.*;
import com.hw.model.common.PageInfo;
import com.hw.model.dto.overview.DetailedQueryDTO;
import com.hw.model.dto.overview.OverviewQueryDTO;
import com.hw.model.entity.DiagTaskPO;
import com.hw.model.entity.OssPO;
import com.hw.model.mapper.DiagTaskMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hw.model.mapper.OssMapper;
import com.hw.model.vo.*;
import com.hw.model.vo.overview.DataDetailVO;
import com.hw.model.vo.overview.PatientDataDetailVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.zip.Adler32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 作者
 * @since 2022-11-24
 */
@Service
@Slf4j
public class DiagTaskServiceImpl extends ServiceImpl<DiagTaskMapper, DiagTaskPO> implements IDiagTaskService {

    @Autowired
    OssMapper ossMapper;

    @Autowired
    AwsOssContext awsOssContext;

    @Autowired
    IOssService ossServiceImpl;

    @Override
    public OverviewVO overview(OverviewQueryDTO overviewQueryDTO) {
        if(CheckUtil.checkTimeHaveSfm(overviewQueryDTO.getStartTime())){
            overviewQueryDTO.setStartTime(overviewQueryDTO.getStartTime() + DateUtils.TIME_BEGIN_SUFFIX);
        }
        if(CheckUtil.checkTimeHaveSfm(overviewQueryDTO.getEndTime())){
            overviewQueryDTO.setEndTime(overviewQueryDTO.getEndTime() + DateUtils.TIME_END_SUFFIX);
        }

        //获取统计数据
        OverviewVO overview = this.baseMapper.overview(Timestamp.valueOf(overviewQueryDTO.getStartTime()), Timestamp.valueOf(overviewQueryDTO.getEndTime()));

        //成功率
        String successRate = "";
        if( overview.getVideoCount() != 0 ){
            BigDecimal successVideo = MathCustomUtils.initBigDecimal(overview.getSuccessVideoCount());
            BigDecimal videoCount = MathCustomUtils.initBigDecimal(overview.getVideoCount());

            BigDecimal val = successVideo.divide(videoCount,2, BigDecimal.ROUND_HALF_UP).setScale(2, RoundingMode.HALF_UP);

            successRate = val.multiply(MathCustomUtils.initBigDecimal(100)).toString() + "%";
        }

        overview.setSuccessRate(successRate);

//        Timestamp.valueOf(overviewQueryDTO.getStartTime());
        //活跃度趋势
        List<ChartsVO> activeTrends = getActiveTrends(overviewQueryDTO);
        overview.setActiveTrends(activeTrends);

        //拍摄数趋势
        List<ChartsVO> numberOfShotsTrends = getNumberOfShotsTrends(overviewQueryDTO);
        overview.setNumberOfShotsTrends(numberOfShotsTrends);

        return overview;
    }

    @Override
    public PageInfo<DataDetailVO> detailed(DetailedQueryDTO detailedQueryDTO) {
        if( detailedQueryDTO == null ){
            detailedQueryDTO = new DetailedQueryDTO();
        }
        if( null == detailedQueryDTO.getPageNum() || detailedQueryDTO.getPageNum() < 1 ){
            detailedQueryDTO.setPageNum(PageInfoContants.PAGE_NUM);
            detailedQueryDTO.setPageSize(PageInfoContants.PAGE_SIZE);
        }

        if( !StrUtil.isEmpty(detailedQueryDTO.getShotDateBegin()) ){
            detailedQueryDTO.setShotDateBeginSql(Timestamp.valueOf(detailedQueryDTO.getShotDateBegin() + DateUtils.TIME_BEGIN_SUFFIX));
            detailedQueryDTO.setShotDateEndSql(Timestamp.valueOf(detailedQueryDTO.getShotDateEnd() + DateUtils.TIME_END_SUFFIX));
        }

        //排序
        String orderSql = getOrderSql(detailedQueryDTO);
        StopWatch stopWatch = StopWatch.create("小孩数据查询");
        stopWatch.start("开始查询数据库");
        Page<DataDetailVO> page = new Page<>(detailedQueryDTO.getPageNum(), detailedQueryDTO.getPageSize());
        Page<DataDetailVO> dataDetailVOPage = this.baseMapper.dataDetail(page, detailedQueryDTO,orderSql);

        stopWatch.stop();
        stopWatch.start("开始转换时间");
        dataDetailVOPage.getRecords().forEach(v->{
            v.setShotDate(DateUtils.formatDateTime(v.getCreatedAt(),"yyyy-MM-dd"));
            v.setShotTime(DateUtils.formatDateTime(v.getCreatedAt(),"HH:mm:ss"));
        });
        stopWatch.stop();

        log.info(stopWatch.prettyPrint(TimeUnit.MILLISECONDS));


        PageInfo<DataDetailVO> res = new PageInfo<>(detailedQueryDTO.getPageNum(), detailedQueryDTO.getPageSize());
        res.setPage(dataDetailVOPage.getTotal(),dataDetailVOPage.getRecords());

        return res;
    }


    /**
     * 获取排序sql
     * @param detailedQueryDTO
     * @return
     */
    private String getOrderSql(DetailedQueryDTO detailedQueryDTO){
        String orderSql = " order by ";
        if( null != detailedQueryDTO.getOrderChileName() && detailedQueryDTO.getOrderChileName() != 0 ){
            if( detailedQueryDTO.getOrderChileName() == 1  ){
                orderSql += "convert_to(child.infant_name,'GBK'),";
            }else{
                orderSql += "convert_to(child.infant_name,'GBK') desc,";
            }
        }
        if( null != detailedQueryDTO.getOrderFamilyName()  && detailedQueryDTO.getOrderFamilyName() != 0){
            if( detailedQueryDTO.getOrderFamilyName() == 1  ){
                orderSql += "convert_to(family.name,'GBK'),";
            }else{
                orderSql += "convert_to(family.name,'GBK') desc,";
            }
        }
        if( null != detailedQueryDTO.getOrderShotDate()   && detailedQueryDTO.getOrderShotDate() != 0){
            if( detailedQueryDTO.getOrderShotDate() == 1  ){
                orderSql += "task.created_at,";
            }else{
                orderSql += "task.created_at desc,";
            }
        }else{
            orderSql += "task.created_at desc,";
        }

        orderSql = orderSql.substring(0, orderSql.length() - 1);
        return orderSql;
    }

    @Override
    public void exportDetailed(DetailedQueryDTO detailedQueryDTO) {
        if( detailedQueryDTO == null ){
            detailedQueryDTO = new DetailedQueryDTO();
        }
        detailedQueryDTO.setPageNum(1);
        detailedQueryDTO.setPageSize(PageInfoContants.PAGE_SIZE_ALL);

        PageInfo<DataDetailVO> detailed = this.detailed(detailedQueryDTO);

        //获取响应体
        HttpServletResponse response = ServletUtil.getResponse();

        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String fileName = UUID.randomUUID().toString() + ".xlsx";
        response.setHeader("Content-disposition", "attachment;filename*=" + fileName);

        detailedQueryDTO.setExportListVal(StrUtil.split(detailedQueryDTO.getExportList(), "_"));

        //获取头部
        if(CollectionUtil.isEmpty(detailedQueryDTO.getExportListVal())){
            List<String> list = new ArrayList<>();
            list.add("id");
            detailedQueryDTO.setExportListVal(list);
        }
        List<List<String>> head = ExportUtil.generateHead(detailedQueryDTO.getExportListVal(), ExportUtil.getDetailedHeadMap());

        //获取数据
        List<List<String>> bodys = ExportUtil.generateDetailedBodys(detailedQueryDTO.getExportListVal(), detailed.getList());


        try {
            EasyExcel.write(response.getOutputStream())
                    .head(head)
                    .sheet("detail")
                    .doWrite(bodys);
        } catch (IOException e) {
            e.printStackTrace();
        }


//        try {
//            EasyExcel.write(response.getOutputStream(), DataDetailVO.class)
//                    .sheet("数据明细")
//                    .doWrite(detailed.getList());
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
    }

    @Override
    public void exportBonesCsv(Long ossId, HttpServletResponse response) {
        if( null == ossId ){
            throw new BaseException("The corresponding id of bone csv is empty");
        }
        OssPO ossPO = ossMapper.selectById(ossId);
        if( null == ossPO ){
            throw new BaseException("The OSS file does not exist");
        }

        String fileName = ossServiceImpl.getOssFileName(ossPO.getIsVideo(), ossPO.getKey(), ossPO.getDiagKey());
        //下载
        awsOssContext.downloadFile(fileName,ossPO.getFilename(),response);

    }

    @Override
    public void batchDownload(DetailedQueryDTO detailedQueryDTO, HttpServletRequest request, HttpServletResponse response) {
        if( detailedQueryDTO == null ){
            detailedQueryDTO = new DetailedQueryDTO();
        }
        detailedQueryDTO.setPageNum(1);
        detailedQueryDTO.setPageSize(PageInfoContants.PAGE_SIZE_ALL);

        PageInfo<DataDetailVO> detailed = this.detailed(detailedQueryDTO);
        List<DataDetailVO> fileList = detailed.getList();
        try {
            // 初始化
            //zip的名称
            String fileName = "batchBonesCsv.zip";
            // 创建临时文件
            File zipFile = File.createTempFile("batchDownload", ".zip");
            FileOutputStream f = new FileOutputStream(zipFile);
            /**
             * 作用是为任何OutputStream产生校验和
             * 第一个参数是制定产生校验和的输出流，第二个参数是指定Checksum的类型 （Adler32（较快）和CRC32两种）
             */
            CheckedOutputStream csum = new CheckedOutputStream(f, new Adler32());
            // 用于将数据压缩成Zip文件格式
            ZipOutputStream zos = new ZipOutputStream(csum);
            //域名
            for (DataDetailVO dataDetailVO :fileList){
                //得到要下载的文件名（Object Name）字符串
                String ossfile = ossServiceImpl.getOssFileName(2,dataDetailVO.getOssKey(),dataDetailVO.getOssKey());

                // 获取Object，返回结果为OSSObject对象
                S3Object ossObject = awsOssContext.getOssClient().getObject(awsOssContext.getOssBucketName(), ossfile);
                // 读去Object内容  返回
                InputStream inputStream = ossObject.getObjectContent();
                // 对于每一个要被存放到压缩包的文件，都必须调用ZipOutputStream对象的putNextEntry()方法，确保压缩包里面文件不同名

                zos.putNextEntry(new ZipEntry(dataDetailVO.getFilename()));
                int bytesRead = 0;
                // 向压缩文件中输出数据
                while((bytesRead=inputStream.read())!=-1){
                    zos.write(bytesRead);
                }
                inputStream.close();
                zos.closeEntry(); // 当前文件写完，定位为写入下一条项目
            }
            zos.close();
            String header = request.getHeader("User-Agent").toUpperCase();
            if (header.contains("MSIE") || header.contains("TRIDENT") || header.contains("EDGE")) {
                fileName = URLEncoder.encode(fileName, "utf-8");
                fileName = fileName.replace("+", "%20");    //IE下载文件名空格变+号问题
            } else {
                fileName = new String(fileName.getBytes(), "ISO8859-1");
            }
            response.reset();
            response.setContentType("text/plain");
            response.setContentType("application/octet-stream; charset=utf-8");
            response.setHeader("Location", fileName);
            response.setHeader("Cache-Control", "max-age=0");
            response.setHeader("Content-Disposition", "attachment; filename=" + fileName);

            FileInputStream fis = new FileInputStream(zipFile);
            BufferedInputStream buff = new BufferedInputStream(fis);
            BufferedOutputStream out=new BufferedOutputStream(response.getOutputStream());
            byte[] car=new byte[1024];
            int l=0;
            while (l < zipFile.length()) {
                int j = buff.read(car, 0, 1024);
                l += j;
                out.write(car, 0, j);
            }
            // 关闭流
            fis.close();
            buff.close();
            out.close();


            // 删除临时文件
            zipFile.delete();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取活跃度趋势
     * @param overviewQueryDTO
     * @return
     */
    private List<ChartsVO> getActiveTrends(OverviewQueryDTO overviewQueryDTO){
        List<OverviewActiveTrendsVO> overviewActiveTrendsVOS = this.baseMapper.activeTrends(Timestamp.valueOf(overviewQueryDTO.getStartTime()), Timestamp.valueOf(overviewQueryDTO.getEndTime()));
        List<ChartsVO> chartsVOS = new ArrayList<>();

        //转换为map : 日期 -> 数据
        Map<String, OverviewActiveTrendsVO> mapTrends = overviewActiveTrendsVOS.stream().collect(Collectors.toMap(OverviewActiveTrendsVO::getCdate, v -> v));

        //日期列表
        List<String> timeList =
                DateUtils.getTimePartitions(DateUtils.parseDateTime(overviewQueryDTO.getStartTime()),
                        DateUtils.parseDateTime(overviewQueryDTO.getEndTime()), TimePartitionEnum.DAY);
//
        String familyName = "家庭数";
        String clientName = "用户数";
        String childName = "孩子数";
        timeList.forEach(date ->{
            ChartsVO<Integer> family = new ChartsVO<>();
            ChartsVO<Integer> client = new ChartsVO<>();
            ChartsVO<Integer> child = new ChartsVO<>();

            family.setDate(date);
            client.setDate(date);
            child.setDate(date);
            family.setCategory(familyName);
            client.setCategory(clientName);
            child.setCategory(childName);

            OverviewActiveTrendsVO overviewActiveTrendsVO = mapTrends.get(date);
            if( null != overviewActiveTrendsVO ){
                family.setValue(overviewActiveTrendsVO.getFcount());
                client.setValue(overviewActiveTrendsVO.getClientCount());
                child.setValue(overviewActiveTrendsVO.getCcount());
            }else{
                family.setValue(0);
                client.setValue(0);
                child.setValue(0);
            }

            chartsVOS.add(family);
            chartsVOS.add(client);
            chartsVOS.add(child);
        });

        List<ChartsVO> res = chartsVOS.stream().sorted(Comparator.comparing(ChartsVO::getCategory)).collect(Collectors.toList());

        return res;
    }

    /**
     * 获取拍摄数趋势
     * @param overviewQueryDTO
     * @return
     */
    private List<ChartsVO> getNumberOfShotsTrends(OverviewQueryDTO overviewQueryDTO){
        List<OverviewPssTrendsVO> overviewPssTrendsVOS = this.baseMapper.numberOfShotsTrends(Timestamp.valueOf(overviewQueryDTO.getStartTime()), Timestamp.valueOf(overviewQueryDTO.getEndTime()));


        //转换为map : 日期 -> 数据
        Map<String, OverviewPssTrendsVO> mapTrends = overviewPssTrendsVOS.stream().collect(Collectors.toMap(OverviewPssTrendsVO::getCdate, v -> v));

        //日期列表
        List<String> timeList =
                DateUtils.getTimePartitions(DateUtils.parseDateTime(overviewQueryDTO.getStartTime()),
                        DateUtils.parseDateTime(overviewQueryDTO.getEndTime()), TimePartitionEnum.DAY);

        List<ChartsVO> chartsVOS = new ArrayList<>();
        timeList.forEach(date ->{
            ChartsVO<Integer> oss = new ChartsVO<>();
            oss.setCategory("拍摄数");
            oss.setDate(date);
            OverviewPssTrendsVO overviewPssTrendsVO = mapTrends.get(date);
            if( null != overviewPssTrendsVO ){
                oss.setValue(overviewPssTrendsVO.getOssCount());
            }else{
                oss.setValue(0);
            }

            chartsVOS.add(oss);
        });

        return chartsVOS;
    }



}
